//
//  CompressionController.m
//  VidCrusher
//
//  Created by Cemil Browne on 5/06/08.
//  Copyright 2008 Cemil Browne. All rights reserved.
//
/*
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */ 

#import "CompressionController.h"
#import "AppController.h"


@implementation CompressionController


// This is the main helper function to do exports.  It grabs the next file, figures out the destination path for it, then hands it off for actual compression. It also zeros out the progress bar. 
-(void) performNextExport:(id) anObject
{
	// curIndex starts at -1, so this is correct. 
	curIndex++;
	if(curIndex >= total) // If we're done compressing, get rid of the sheet. 
	{
		[overallProgressIndicator setDoubleValue:0.0];
		[fileProgressIndicator setDoubleValue:0.0];
		[NSApp endSheet:compressionSheet];
		[compressionSheet orderOut:self];
		[dockProgressIndicator setHidden:YES];
		return;
	}
	
	componentList = [self availableComponents]; // Available QuickTime components.  (same list, really, as Quicktime Pro File->Export)

	NSFileManager *fileManager = [NSFileManager defaultManager];
	BOOL isDir, exists;
	int settingIndex = [self findComponent:[compressFor titleOfSelectedItem]]; // Grab the compressFor - iPod/iPhone at the moment. 
	if(settingIndex == -1) // If not found, get out.  
	{
		// Get out of here
		[NSApp endSheet:compressionSheet];
		[compressionSheet orderOut:self];
		return;
	}
	
	VideoItem* temp = [arrangedObjectEnumerator nextObject]; // Grab the top item in the list
	NSString *destination = [[[destinationPath stringValue] stringByExpandingTildeInPath] stringByStandardizingPath];  // Expand the destination.  
	
	if([destination length] == 0) // if destination isn't set
	{
		destination = [temp path]; // Grab the original path. 
	}
	else // Destination is set
	{
		exists = [fileManager fileExistsAtPath:destination isDirectory:&isDir]; 
		if(exists && isDir) // If its a valid directory
		{
			destination = [destination stringByAppendingPathComponent:[temp fileName]]; // add the filename to the directory.
		}
		else
		{
			destination = [temp path];
		}
		
	}
	
	exists = [fileManager fileExistsAtPath:[temp path] isDirectory:&isDir]; // If the file exists + is openable
	if(exists && !isDir)
	{
		QTMovie *tempMovie = [temp movie]; // Grab the quicktime movie
		
		NSString *baseName = [destination stringByDeletingPathExtension]; // remove the extension, add .m4v
		NSString *newName = [baseName stringByAppendingPathExtension:@"m4v"];

		while([fileManager fileExistsAtPath:newName]) // If the file exists, add a -temp to it
		{
			baseName = [baseName stringByAppendingString:@"-temp"];
			
			newName = [baseName stringByAppendingPathExtension:@"m4v"];
		} 
		
		_curTag = [[NSString alloc] initWithFormat:@"Compressing %@: ", [temp fileName]]; // Current tag for second progress bar. 

		[overallProgressLabel setStringValue:[NSString stringWithFormat:@"Compressing file %d of %d", curIndex+1, total]];
		[fileProgressLabel setStringValue:[NSString stringWithFormat:@"%@ 0%%", _curTag]];
		
		[self writeMovie:tempMovie toFile:newName withComponent:[componentList objectAtIndex:settingIndex] withExportSettings:nil withVideoItem:temp]; // Begin writing the file. 
	}
	

}

// Kick off the compression sequence.  
-(void) beginCompressing
{
	// Don't stop yet!
	shouldStop = 0;
	
	// Set up progressIndicators. 
	[fileProgressIndicator setUsesThreadedAnimation:YES];
	[overallProgressIndicator setUsesThreadedAnimation:YES];
	dockProgressIndicator = [[UKDockProgressIndicator alloc] init];
	[dockProgressIndicator setHidden:NO];
	total = [[myArrayController arrangedObjects] count];
	arrangedObjectEnumerator = [[myArrayController arrangedObjects] objectEnumerator];
	curIndex = -1;
	
	[self performNextExport:self];
}

// Search the array for the component.  
- (int) findComponent:(NSString *)name
{
	int i;
	for (i=0; i<[componentList count]; i++) {
		if ([[[componentList objectAtIndex:i] objectForKey:@"name"] isEqualToString:name]) {
			return i;
		}
	}
	return -1;
}

// Just bundles up the arguments into one array so I can call doThreadedWrite.  
- (BOOL)writeMovie:(QTMovie *)movie toFile:(NSString *)file withComponent:(NSDictionary *)component withExportSettings:(NSData *)exportSettings withVideoItem:(VideoItem *)aVideoItem
{
	// Detach the movie.  Needed for multi-threading. 
	[movie detachFromCurrentThread];
	
	// Bundle 'em up
	NSArray *args = [NSArray arrayWithObjects:movie, file, component, aVideoItem, exportSettings, nil];
	
	// Kick off the thread to do the compression.  
	[NSThread detachNewThreadSelector:@selector(doThreadedWrite:) toTarget:self withObject:args];
	return NO;
}

// Compression thread. 
- (void) doThreadedWrite:(NSArray *) args
{
	// Must be set up at thread to avoid errors. 
	NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
	
	// Grab the args.  
	QTMovie *movie = [args objectAtIndex:0];
	NSString *file = [args objectAtIndex:1];
	NSDictionary *component = [args objectAtIndex:2];
	VideoItem *tempVideoItem = [args objectAtIndex:3];
	NSData *exportSettings = nil;
	
	[QTMovie enterQTKitOnThread]; // Make it thread-safe. 
	[movie attachToCurrentThread];
	
	// Needed for progress updates. 
	[movie setDelegate:self];
	
	// Set up the attributes.  Easy enough.  
	NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
								[NSNumber numberWithBool:YES], QTMovieExport,
								[component objectForKey:@"subtype"], QTMovieExportType,
								[component objectForKey:@"manufacturer"], QTMovieExportManufacturer,
								exportSettings, QTMovieExportSettings,
								// do not set the QTMovieFlatten flag! (causes export settings to be ignored)
								nil];
	
	BOOL result = [movie writeToFile:file withAttributes:attributes];
	
	[movie detachFromCurrentThread]; // Stop the movie on the thread. 
	[QTMovie exitQTKitOnThread];
	
	if(result)  // Write it!  no nil if it succeeds.  
		{		
		// Kick off the next export while I'm doing everything else.  No point in waiting. 
		[self performSelectorOnMainThread:@selector(performNextExport:) withObject:self	waitUntilDone:NO];
		
		// Find the path to atomicparsley.  http://atomicparsley.sourceforge.net/ SVN version required.  Also must add tag for .m4v in it.  
		NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; 
		resourcePath = [resourcePath stringByAppendingPathComponent:@"AtomicParsley"];
		
		// We're going to fork a process to override the atom information using AtomicParsley.  
		NSTask *fixAtoms = [[NSTask alloc] init];
		[fixAtoms setLaunchPath:resourcePath];
		
		// Set up the arguments.  Check AtomicParsley's documents for the arguments.  
		NSMutableArray *arguments = [[NSMutableArray alloc] init];
		[arguments addObject:file];
		[arguments addObject:@"--stik"];
		NSString *videoType = [[[[AppController alloc] init] videoTypes] objectAtIndex:[tempVideoItem videoType]];
		[arguments addObject:videoType];
		if([videoType isEqualToString:@"TV Show"])
		{
			[arguments addObject:@"--TVShowName"];
			[arguments addObject:[tempVideoItem showName]];
			[arguments addObject:@"--TVEpisodeNum"];
			[arguments addObject:[tempVideoItem episodeNumber]];
			[arguments addObject:@"--TVSeasonNum"];
			[arguments addObject:[tempVideoItem seasonNumber]];
		}
		[arguments addObject:@"--overWrite"];
		
		// Start the fixing. 
		[fixAtoms setArguments:arguments];
		[fixAtoms launch];
		
		// Wait for it to be done before adding to iTunes
		[fixAtoms waitUntilExit];
		
		// Add to iTunes if needed.   
		if([addToItunes state] == NSOnState)
		{
			[self checkForiTunes];
			[self importMusicFile:file];
			if([deleteAfterAdding state] == NSOnState)
			{
				[[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[file stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[file lastPathComponent]] tag:0];
			}
		}
		
		// Pop it off the array
		[myArrayController removeObject:tempVideoItem];
		
	}
	
	// Exit the thread.  
	[autoreleasepool release];
	[NSThread exit];

}


// Check whether iTunes is running already.  Just trundle through the launchedApplications array to find it. 
- (void)checkForiTunes
{
	int iTunesRunning = 0;
	NSArray *runningApps = [[NSWorkspace sharedWorkspace] launchedApplications];
	int count = [runningApps count];
	for(int i=0;i<count;i++)
	{
		if([[[runningApps objectAtIndex:i] valueForKey:@"NSApplicationName"] isEqualToString:@"iTunes"])
		{
			iTunesRunning = 1;
		}
	}
	if(!iTunesRunning)
	{
		[[NSWorkspace sharedWorkspace] launchApplication:@"iTunes"];
	}
}


// This code comes form http://www.cocoadev.com/index.pl?QTMovieExportSettings
- (NSArray *)availableComponents
{
	NSMutableArray *array = [NSMutableArray array];
	
	ComponentDescription cd;
	Component c;
	
	cd.componentType = MovieExportType;
	cd.componentSubType = 0;
	cd.componentManufacturer = 0;
	cd.componentFlags = canMovieExportFiles;
	cd.componentFlagsMask = canMovieExportFiles;
	
	while((c = FindNextComponent(c, &cd)))
	{
		Handle name = NewHandle(4);
		ComponentDescription exportCD;
		
		if (GetComponentInfo(c, &exportCD, name, nil, nil) == noErr)
		{
			char *namePStr = *name;
			NSString *nameStr = [[NSString alloc] initWithBytes:&namePStr[1] length:namePStr[0] encoding:NSUTF8StringEncoding];
			
			NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
										nameStr, @"name",
										[NSData dataWithBytes:&c length:sizeof(c)], @"component",
										[NSNumber numberWithLong:exportCD.componentType], @"type",
										[NSNumber numberWithLong:exportCD.componentSubType], @"subtype",
										[NSNumber numberWithLong:exportCD.componentManufacturer], @"manufacturer",
										nil];
			[array addObject:dictionary];
			[nameStr release];
		}
		
		DisposeHandle(name);
	}
	componentList = [array copy];
	return array;
}

// This function is called form the movie on a regular basis during export (because this is the delegate).  It updates the progress bar. 
- (BOOL)movie:(QTMovie *)movie 
shouldContinueOperation:(NSString *)op 
	withPhase:(QTMovieOperationPhase)phase 
	atPercent:(NSNumber *)percent 
withAttributes:(NSDictionary *)attributes
{
	// Update PI
	[fileProgressIndicator setDoubleValue:([percent doubleValue] * 100)];
	
	// Update the *main* progress bar. 
	[self performSelectorOnMainThread:@selector(updateOverAllProgressBar:) withObject:percent waitUntilDone:NO];
	
	// Update the label. 
	[fileProgressLabel setStringValue:[NSString stringWithFormat:@"%@ %d%%", _curTag, (int)round([percent doubleValue] * 100)]];
	if(shouldStop == 1) // This is set by the AppController if stop compressing is clicked.  
	{
		[dockProgressIndicator setHidden:YES];
		return false;
	}
	return true;
}

// Update top progress bar.   Called above.  
- (void)updateOverAllProgressBar: (NSNumber *)percent
{
	double curPer = [percent doubleValue];
	double percentage = curIndex*(100/total) + curPer*(100/total);
	[overallProgressIndicator setDoubleValue:percentage];
	[dockProgressIndicator setDoubleValue:percentage];
	[dockProgressIndicator updateDockTile];
}

// This code from: http://lists.apple.com/archives/cocoa-dev/2007/Jun/msg01060.html  Thanks to Gregory Weston for the code on the cocoa-dev mailing list.  
- (void)importMusicFile:(NSString*)inPath
{
	const char* kTemplate = "'insh':'obj '{form:indx, want:type(cLiP),"
	"seld:long(1), from:'null'()}, '----':alis (@@)";
	
	CFURLRef theFileURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault,
														 (CFStringRef)inPath,
														 kCFURLPOSIXPathStyle, false);
	if(theFileURL)
	{
		FSRef theFileRef = {};
		if(CFURLGetFSRef(theFileURL, &theFileRef))
		{
			AliasHandle theFileAlias = NULL;
			OSStatus theStatus = FSNewAlias(NULL, &theFileRef,&theFileAlias);
			if(theStatus == noErr)
			{
				OSType theSignature = 'hook'; // The app signature for iTunes
				AppleEvent theRequest = {typeNull, NULL};
				AEBuildError theBuildError = {};
				theStatus = AEBuildAppleEvent('hook', 'Add ', typeApplSignature,
											  &theSignature, sizeof(OSType),
											  kAutoGenerateReturnID, kAnyTransactionID,
											  &theRequest, &theBuildError, kTemplate,
											  theFileAlias);
				if(theStatus == noErr)
				{
					AppleEvent theReply = {typeNull, NULL};
					theStatus = AESendMessage(&theRequest, &theReply, kAEWaitReply,
											  kNoTimeOut);
					if(theStatus == noErr)
					{
						(void)AEDisposeDesc(&theReply);
					}
					(void)AEDisposeDesc(&theRequest);
				}
				DisposeHandle((Handle)theFileAlias);
			}
		}
		CFRelease(theFileURL);
	}
}
@end
